#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/syscall.h>
#if !defined(__UCLIBC__)
#include <link.h>
#include <execinfo.h>
#endif

#include "debug_backtrace.h"

#define BT_SIZE  (100)
#define CMD_SIZE (1024)

#if 0
/*
 * from /usr/include/bits/signum.h
 */
static int signals_all[] = {
    SIGHUP,  /* 1 Hangup (POSIX).  */
    SIGINT,  /* 2 Interrupt (ANSI).  */
    SIGQUIT, /* 3 Quit (POSIX).  */
    SIGILL,  /* 4 Illegal instruction (ANSI).  */
    SIGTRAP, /* 5 Trace trap (POSIX).  */
    SIGABRT, /* 6 Abort (ANSI).  */
    SIGIOT,  /* 6 IOT trap (4.2 BSD).  */
    SIGBUS,  /* 7 BUS error (4.2 BSD).  */
    SIGFPE,  /* 8 Floating-point exception (ANSI).  */
    SIGKILL, /* 9 Kill, unblockable (POSIX).  */
    SIGUSR1, /* 10 User-defined signal 1 (POSIX).  */
    SIGSEGV, /* 11 Segmentation violation (ANSI).  */
    SIGUSR2, /* 12 User-defined signal 2 (POSIX).  */
    SIGPIPE, /* 13 Broken pipe (POSIX).  */
    SIGALRM, /* 14 Alarm clock (POSIX).  */
    SIGTERM, /* 15 Termination (ANSI).  */
#if !defined(ENV_MINGW)
    SIGSTKFLT, /* 16 Stack fault.  */
#endif
    SIGCLD,    /* 17 Same as SIGCHLD (System V).  */
    SIGCHLD,   /* 17 Child status has changed (POSIX).  */
    SIGCONT,   /* 18 Continue (POSIX).  */
    SIGSTOP,   /* 19 Stop, unblockable (POSIX).  */
    SIGTSTP,   /* 20 Keyboard stop (POSIX).  */
    SIGTTIN,   /* 21 Background read from tty (POSIX).  */
    SIGTTOU,   /* 22 Background write to tty (POSIX).  */
    SIGURG,    /* 23 Urgent condition on socket (4.2 BSD).  */
    SIGXCPU,   /* 24 CPU limit exceeded (4.2 BSD).  */
    SIGXFSZ,   /* 25 File size limit exceeded (4.2 BSD).  */
    SIGVTALRM, /* 26 Virtual alarm clock (4.2 BSD).  */
    SIGPROF,   /* 27 Profiling alarm clock (4.2 BSD).  */
    SIGWINCH,  /* 28 Window size change (4.3 BSD, Sun).  */
    SIGPOLL,   /* 29 Pollable event occurred (System V).  */
    SIGIO,     /* 29 I/O now possible (4.2 BSD).  */
    SIGPWR,    /* 30 Power failure restart (System V).  */
    SIGSYS,    /* 31 Bad system call.  */
};
#endif

static int signals_trace[] = {
    SIGILL,  /* Illegal instruction (ANSI).  */
    SIGABRT, /* Abort (ANSI).  */
    SIGBUS,  /* BUS error (4.2 BSD). (unaligned access) */
    SIGFPE,  /* Floating-point exception (ANSI).  */
    SIGSEGV, /* Segmentation violation (ANSI).  */
};

#if !defined(__UCLIBC__)
static void *get_uc_mcontext_pc(ucontext_t *uc)
{
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
/* OSX < 10.6 */
#if defined(__x86_64__)
    return (void *)uc->uc_mcontext->__ss.__rip;
#elif defined(__i386__)
    return (void *)uc->uc_mcontext->__ss.__eip;
#else
    return (void *)uc->uc_mcontext->__ss.__srr0;
#endif
#elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
/* OSX >= 10.6 */
#if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
    return (void *)uc->uc_mcontext->__ss.__rip;
#else
    return (void *)uc->uc_mcontext->__ss.__eip;
#endif
#elif defined(__linux__)
/* Linux */
#if defined(__i386__)
    return (void *)uc->uc_mcontext.gregs[REG_EIP]; /* Linux 32 */
#elif defined(__X86_64__) || defined(__x86_64__)
    return (void *)uc->uc_mcontext.gregs[REG_RIP]; /* Linux 64 */
#elif defined(__ia64__) /* Linux IA64 */
    return (void *)uc->uc_mcontext.sc_ip;
#elif defined(__arm__)
    return (void *)uc->uc_mcontext.arm_pc;
#elif defined(__aarch64__)
    return (void *)uc->uc_mcontext.pc;
#else
    return NULL;
#endif
#else
    return NULL;
#endif
}
#endif

#if !defined(__UCLIBC__)

static void backtrace_symbols_detail(void *array[], int size)
{
    int i = 0;
    char cmd[CMD_SIZE] = "addr2line -C -f -e ";
    char *prog = cmd + strlen(cmd);
    ssize_t r = readlink("/proc/self/exe", prog, sizeof(cmd) - strlen(cmd) - 1);
    if (r == -1)
    {
        printf("backtrace_symbols_detail unsupported!\n");
        perror("readlink");
        return;
    }
    prog[r] = '\0';
    FILE *fp = popen(cmd, "w");
    if (!fp)
    {
        perror("popen");
        return;
    }

    for (i = 0; i < size; ++i)
    {
        Dl_info dl_info;
        if (dladdr(array[i], &dl_info) && dl_info.dli_fbase)
        {
            uintptr_t offset = (uintptr_t)array[i] - (uintptr_t)dl_info.dli_fbase;
            fprintf(fp, "%lx\n", offset);
        }
        else
        {
            fprintf(fp, "%lx\n", (uintptr_t)array[i]);
        }
    }
    pclose(fp);
}
#endif

#if 0
#include <stacktrace>
#include <iostream>
#include <print>

void print_stack_trace()
{
    auto st = std::stracktrace::current();
    for (const auto & entry : st)
    {
        std::println("Func: {}\t File: {}\t Line: {}\n", entry.description(), entry.source_file(), entry.source_line());
    }
}
#endif

static void backtrace_handler(int sig_num, siginfo_t *info, void *ucontext)
{
    (void)info;
    pid_t crashed_pid = getpid();

#if !defined(__UCLIBC__)
    void *array[BT_SIZE] = {};
    int i = 0, size = 0;
    char **strings = NULL;
    void *caller_addr = get_uc_mcontext_pc((ucontext_t *)ucontext);

    printf("========================================\n");
    printf("===        CRASH HANDLER             ===\n");
    printf("========================================\n");
    printf("Program PID[%d] received Signal [%d](%s) at [%p]\n\n", crashed_pid, sig_num, strsignal(sig_num), caller_addr);

    size = backtrace(array, BT_SIZE);
    if (size <= 0)
    {
        printf("backtrace fail\n");
    }
    else
    {
        if (size > 2)
        {
            // skip 2 frame(backtrace and libc)
            void *p_array = array + 2;
            int new_size = size - 2;
            strings = backtrace_symbols(p_array, new_size);
            if (strings)
            {
                printf("Backtrace:\n");
                for (i = 0; i < new_size; ++i)
                {
                    printf("#%d  %s\n", i, strings[i]);
                }
                free(strings);
            }

            printf("\nSource locations:\n");
            backtrace_symbols_detail(p_array, new_size);
        }
    }
#endif

    struct sigaction sa = {};
    sa.sa_handler = SIG_DFL;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(sig_num, &sa, NULL) != 0)
    {
        printf("sigaction failed to set default handler\n");
        return;
    }

    sigset_t mask = {};
    sigemptyset(&mask);
    sigaddset(&mask, sig_num);
    if (sigprocmask(SIG_UNBLOCK, &mask, NULL) != 0)
    {
        printf("sigprocmask failed to unblock signal\n");
        return;
    }

    raise(sig_num); // for core dump
}

#ifndef NDEBUG
int debug_backtrace_init(void)
{
    size_t i = 0;
    struct sigaction sa = {};
    sa.sa_sigaction = backtrace_handler;
    sa.sa_flags = SA_RESTART | SA_SIGINFO;
    sigemptyset(&sa.sa_mask);

    int ret = 0;
    for (i = 0; i < (sizeof(signals_trace) / sizeof(int)); ++i)
    {
        if (sigaction(signals_trace[i], &sa, NULL) != 0)
        {
            printf("backtrace failed to set signal handler for %d!\n", signals_trace[i]);
            ret = -1;
            break;
        }
    }
    return ret;
}
#else
int debug_backtrace_init(void)
{
    (void)signals_trace;
    void (*singnal_cb)(int, siginfo_t *, void *) = backtrace_handler;
    (void)singnal_cb;

    return 0;
}
#endif